/**   
* @Title: DynamicQueryImpl.java
* @Package org.xaoyaoyao.cloud.jpa.dynamic.impl
* @Description: TODO
* @author xaoyaoyao
* @date Feb 11, 2019 3:26:18 PM
* @version V1.0   
*/
package org.xaoyaoyao.cloud.jpa.dynamic.impl;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.persistence.Column;
import javax.persistence.EntityManager;
import javax.persistence.Id;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.Table;

import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.query.NativeQuery;
import org.hibernate.transform.Transformers;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import org.xaoyaoyao.cloud.jpa.adapter.BeanTransformerAdapter;
import org.xaoyaoyao.cloud.jpa.dynamic.DynamicQuery;

import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName: DynamicQueryImpl
 * @Description: TODO
 * @author xaoyaoyao
 * @date Feb 11, 2019 3:26:18 PM
 * 
 */
@Slf4j
@Repository
public class DynamicQueryImpl implements DynamicQuery {

	@PersistenceContext
	private EntityManager entityManager;

	public EntityManager getEntityManager() {
		return entityManager;
	}

	@Override
	@Transactional
	public void save(Object entity) {
		if (null != entity) {
			entityManager.persist(entity);
		}
	}

	@Override
	@Transactional
	public void update(Object entity) {
		if (null != entity) {
			entityManager.merge(entity);
		}
	}

	@Override
	@Transactional
	@SuppressWarnings({ "rawtypes" })
	public int updateByPrimaryKey(Object entity) {
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>>> entity >> {}", entity);
		}
		Assert.notNull(entity, "The entity is empty!");
		Class clazz = entity.getClass();
		String tableName = getTableName(clazz);
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>> Table name is empty!!!");
		}
		Assert.notNull(tableName, "The " + clazz.getName() + " table name is empty!");
		String primaryColumnKey = getPrimaryKey(clazz);
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>> Primary key is empty!!!");
		}
		Assert.notNull(primaryColumnKey, "The " + clazz.getName() + " primary key is empty!");
		// 属性值
		Map<String, Object> properties = getTableProperties(entity, clazz);
		Assert.notEmpty(properties, "The " + clazz.getName() + " attribute values are all empty!");
		Object primaryKeyVal = properties.get(primaryColumnKey);
		Assert.notNull(primaryKeyVal, "The primary key of the " + tableName + " table is empty!");
		return updateValue(properties, tableName, primaryColumnKey, primaryKeyVal);
	}

	@Override
	@Transactional
	@SuppressWarnings({ "rawtypes" })
	public int updateSelective(Object entity, Object primaryKeyVal) {
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>>> entity >> {}, primaryKeyVal >> {}", primaryKeyVal, entity);
		}
		Assert.notNull(primaryKeyVal, "The primaryKeyVal is empty!");
		Assert.notNull(entity, "The entity is empty!");
		Class clazz = entity.getClass();
		String tableName = getTableName(clazz);
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>> Table name is empty!!!");
		}
		Assert.notNull(tableName, "The " + clazz.getName() + " table name is empty!");
		String primaryColumnKey = getPrimaryKey(clazz);
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>> Primary key is empty!!!");
		}
		Assert.notNull(primaryColumnKey, "The " + clazz.getName() + " primary key is empty!");
		// 属性值
		Map<String, Object> properties = getTableProperties(entity, clazz);
		Assert.notEmpty(properties, "The " + clazz.getName() + " attribute values are all empty!");
		return updateValue(properties, tableName, primaryColumnKey, primaryKeyVal);
	}

	private int updateValue(Map<String, Object> properties, String tableName, String primaryColumnKey, Object primaryKeyVal) {
		// 拼接updateSQL语句
		int size = properties.size(), index = 0;
		List<Object> params = new ArrayList<>(size);
		StringBuffer nativeSql = new StringBuffer();
		nativeSql.append("UPDATE ").append(tableName).append(" SET ");
		for (Map.Entry<String, Object> entry : properties.entrySet()) {
			index++;
			String key = entry.getKey();
			Object val = entry.getValue();
			nativeSql.append(key).append(" = ").append("?");
			params.add(val);
			if (index < size) {
				nativeSql.append(", ");
			}
		}
		nativeSql.append(" WHERE ").append(primaryColumnKey).append(" = ?");
		params.add(primaryKeyVal);
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>>> sql >> {}, params >> {}", nativeSql.toString(), params);
		}
		// 执行update语句
		return this.executeUpdate(nativeSql.toString(), params.toArray());
	}

	@SuppressWarnings("rawtypes")
	private String getPrimaryKey(Class clazz) {
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Id.class)) {
				Column declaredAnnotation = field.getDeclaredAnnotation(Column.class);
				String column = declaredAnnotation.name();
				if (StringUtils.isNotBlank(column)) {
					return field.getName();
				}
			}
		}
		return null;
	}

	@Override
	@Transactional
	@SuppressWarnings({ "rawtypes" })
	public int update(Object setEntity, Object whereEntity) {
		Assert.notNull(setEntity, "The setEntity is empty!");
		Assert.notNull(whereEntity, "The whereEntity is empty!");
		Class clazz = setEntity.getClass();
		String tableName = getTableName(clazz), name = getTableName(whereEntity.getClass());
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>> Table name is empty!!!");
		}
		Assert.notNull(tableName, "The " + clazz.getName() + " table name is empty!");
		// 两个表名不一致则跳出
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>> setEntity tableName >> {}, whereEntity tableName >> {}, Inconsistent two table names!!!", tableName, name);
		}
		Assert.isTrue(tableName.equals(name), "Two different table names: setEntity table name: " + tableName + ", whereEntity table name: " + name);
		// 属性值
		Map<String, Object> setProperties = getTableProperties(setEntity, clazz);
		Assert.notEmpty(setProperties, "The " + clazz.getName() + "<setEntity> updated attribute values are all empty!");
		Map<String, Object> whereProperties = getTableProperties(whereEntity, clazz);
		Assert.notEmpty(whereProperties, "The " + clazz.getName() + "<whereEntity> update attribute values are all empty!");
		// 拼接updateSQL语句
		int setSize = setProperties.size(), index = 0, whereSize = whereProperties.size();
		int total = setSize + whereSize;
		List<Object> params = new ArrayList<>(total);
		StringBuffer nativeSql = new StringBuffer();
		nativeSql.append("UPDATE ").append(tableName).append(" SET ");
		// set部分
		for (Map.Entry<String, Object> entry : setProperties.entrySet()) {
			index++;
			String key = entry.getKey();
			Object val = entry.getValue();
			nativeSql.append(key).append(" = ").append("?");
			params.add(val);
			if (index < setSize) {
				nativeSql.append(", ");
			}
		}
		// where部分
		index = 0;
		nativeSql.append(" WHERE ");
		for (Map.Entry<String, Object> entry : whereProperties.entrySet()) {
			index++;
			String key = entry.getKey();
			Object val = entry.getValue();
			nativeSql.append(key).append(" = ").append("?");
			params.add(val);
			if (index < whereSize) {
				nativeSql.append(" AND ");
			}
		}
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>>> sql >> {}, params >> {}", nativeSql.toString(), params);
		}
		// 执行update语句
		return this.executeUpdate(nativeSql.toString(), params.toArray());
	}

	@SuppressWarnings("rawtypes")
	private Map<String, Object> getTableProperties(Object entity, Class clazz) {
		Map<String, Object> properties = new ConcurrentHashMap<>();
		Map<String, String> data = getColumnNames(clazz);
		// 获取Bean
		BeanWrapper srcBean = new BeanWrapperImpl(entity);
		// 获取Bean的属性描述
		PropertyDescriptor[] pds = srcBean.getPropertyDescriptors();
		for (PropertyDescriptor propertyDescriptor : pds) {
			String propertyName = propertyDescriptor.getName();
			Object propertyValue = srcBean.getPropertyValue(propertyName);
			if (log.isDebugEnabled()) {
				log.debug(">>>>>>>>> propertyName >> {}, propertyValue >> {}", propertyName, propertyValue);
			}
			// 只获取非空的属性
			if (null != propertyValue) {
				// 获取属性对应的字段
				String columnName = getColumnName(data, propertyName);
				if (StringUtils.isNotBlank(columnName)) {
					properties.put(columnName, propertyValue);
				}
			}
		}
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>>> properties >> {}", properties);
		}
		return properties;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private String getTableName(Class clazz) {
		Table table = (Table) clazz.getAnnotation(Table.class);
		if (log.isDebugEnabled()) {
			log.debug(">>>>>>>>> table >> {}", table);
		}
		if (null != table) {
			return table.name();
		}
		return null;
	}

	private int executeUpdate(String sql, Object... params) {
		Query query = this.createNativeQuery(sql, params);
		if (null != query) {
			return query.executeUpdate();
		}
		return 0;
	}

	@SuppressWarnings("rawtypes")
	private Map<String, String> getColumnNames(Class clazz) {
		Map<String, String> data = new ConcurrentHashMap<>();
		Field[] fields = clazz.getDeclaredFields();
		for (Field field : fields) {
			if (field.isAnnotationPresent(Column.class)) {
				// 获取字段名
				Column declaredAnnotation = field.getDeclaredAnnotation(Column.class);
				String column = declaredAnnotation.name();
				if (StringUtils.isNotBlank(column)) {
					data.put(field.getName(), column);
				}
			}
		}
		return data;
	}

	private String getColumnName(Map<String, String> data, String fieldName) {
		// 去掉类属性
		if (StringUtils.isBlank(fieldName) || fieldName.toUpperCase().equals("CLASS") || MapUtils.isEmpty(data)) {
			return null;
		}
		return data.get(fieldName);
	}

	@Override
	@Transactional
	public <T> void delete(Class<T> entityClass, Object entityid) {
		if (null != entityid) {
			delete(entityClass, new Object[] { entityid });
		}
	}

	@Override
	@Transactional
	public <T> void delete(Class<T> entityClass, Object[] entityids) {
		if (null != entityClass && null != entityids && entityids.length > 0) {
			for (Object id : entityids) {
				entityManager.remove(entityManager.getReference(entityClass, id));
			}
		}
	}

	@SuppressWarnings({ "unchecked", "deprecation" })
	@Override
	public <T> List<T> nativeQueryList(String nativeSql, Object... params) {
		Query q = this.createNativeQuery(nativeSql, params);
		if (null != q) {
			q.unwrap(NativeQuery.class).setResultTransformer(Transformers.TO_LIST);
			return q.getResultList();
		}
		return null;
	}

	@SuppressWarnings({ "unchecked", "deprecation" })
	@Override
	public <T> List<T> nativeQueryListMap(String nativeSql, Object... params) {
		Query q = this.createNativeQuery(nativeSql, params);
		if (null != q) {
			q.unwrap(NativeQuery.class).setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
			return q.getResultList();
		}
		return null;
	}

	@SuppressWarnings({ "unchecked", "deprecation", "rawtypes" })
	@Override
	public <T> List<T> nativeQueryListModel(Class<T> resultClass, String nativeSql, Object... params) {
		Query q = this.createNativeQuery(nativeSql, params);
		if (null != q) {
			q.unwrap(NativeQuery.class).setResultTransformer(new BeanTransformerAdapter(resultClass));
			return q.getResultList();
		}
		return null;
	}

	@Override
	public Long nativeQueryCount(String nativeSql, Object... params) {
		Object count = createNativeQuery(nativeSql, params).getSingleResult();
		if (null != count) {
			return ((Number) count).longValue();
		}
		return 0L;
	}

	private Query createNativeQuery(String sql, Object... params) {
		if (null != sql) {
			Query q = entityManager.createNativeQuery(sql);
			if (null != params && params.length > 0) {
				for (int index = 0; index < params.length; index++) {
					q.setParameter(index + 1, params[index]);// JPA从1开始计算，注意
				}
			}
			return q;
		}
		return null;
	}
}
